import 'dart:convert';
import 'dart:io';

import 'package:k3cloud_api/params/imp/login.dart';
import 'package:k3cloud_api/params/model/login_info.dart';
import 'package:k3cloud_api/params/request_param.dart';


/// An Api client of the Kingdee Cloud. It is a singleton.
class K3Client {
  
  static K3Client? _instance;

  K3Client._internal();

  factory K3Client.getInstance() => _getInstance();

  static K3Client _getInstance() {
    _instance ??= K3Client._internal();
    return _instance!;
  }

  String? _domain;
  final String _prefix = "/K3Cloud/Kingdee.BOS.WebApi.ServicesStub.";
  final String _suffix = ".common.kdsvc";
  List<Cookie> _cookies = [];
  /// Get the current logined information.
  LoginInfo? loginInfo;

  /// This method will configure the address of the request
  /// 
  /// [domain] The IP or domain of a server which is runing the Kingdee Cloud.
  K3Client setDomain(String domain) {
    _domain = domain;
    return this;
  }

  Future<HttpClientResponse> _postJson(String url, String json) async {
    var client = HttpClient();
    var request = await client.postUrl(Uri.parse(url));
    request.headers.add("content-type", "application/json");
    if (_cookies.isNotEmpty) request.cookies.addAll(_cookies);
    request.add(utf8.encode(json));
    var response = await request.close();
    client.close();
    return response;
  }

  /// Before using the other api, you need login firstly.
  /// This method will fetch cookies and save them to the instance of [K3Client].
  /// When you use the other api, the cookies will send automaticallly.
  /// 
  /// [param] For building the parameters to request the interface of login.
  /// 
  /// At last, the response will be returned as the json Map format.
  Future<LoginInfo> login(LoginParam param) async {
    if (_domain == null) throw Exception("domain msut be not null");

    _cookies.clear();
    String url = _domain! + _prefix + param.getRequestPath() + _suffix;
    var resp = await _postJson(url, jsonEncode(param.toJson()));
    
    if (resp.statusCode != HttpStatus.ok) throw Exception("http error, http status code is ${resp.statusCode}.");
    // 获取cookie
    _cookies = resp.cookies;
    String body = await resp.transform(utf8.decoder).join();
    LoginInfo info = LoginInfo.fromJson(param.parseResponse(body));
    loginInfo = info;
    return info;
  }

  /// This method is used to request the other insterface but login.
  /// 
  /// [param] For building the request paramaters. You should use the implemented class.
  Future<Map<String, dynamic>> operate(RequestParam param) async {
    if (_domain == null) throw Exception("domain msut be not null");
    String url = _domain! + _prefix + param.getRequestPath() + _suffix;
    var resp = await _postJson(url, jsonEncode(param.toJson()));
    if (resp.statusCode != HttpStatus.ok) throw Exception("http error, http status code is ${resp.statusCode}.");
    String body = await resp.transform(utf8.decoder).join();
    return param.parseResponse(body);
  }

}